// 工具方法
const isObject = (val) => val !== null && typeof val === "object";

/*
  1. reactive：用来将数据定义成响应式
    当读取属性的值时，会触发get
      通过 Reflect.get(target, key, receiver) 得到属性的值并返回
      通过 track 进行依赖收集

    当更新属性的值时，会触发set
      通过 Reflect.set(target, key, value, receiver); 设置属性的值并返回
      通过 trigger 来触发用户界面更新
*/
export function reactive(target) {
  if (!isObject(target)) return target;

  const handler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      track(target, key); // 依赖收集
      if (isObject(result)) {
        return reactive(result);
      }
      return result;
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, reactive);
      let result = true;
      if (oldValue !== value) {
        result = Reflect.set(target, key, value, receiver);
        trigger(target, key); // 触发依赖更新
      }
      return result;
    },
  };

  return new Proxy(target, handler);
}

/*
  2. track：用来依赖收集
    如果你会vue2的响应式原理，那么可能知道一个dep.depend方法，用来建立dep和watcher的联系，
    有了这个联系，将来能通过dep找到watcher，从而调用watcher上更新用户界面的方法来达到响应式

    而Vue3的track也是类似的事，他要建立的是响应式数据和更新用户界面方法effect的联系
    整体
      targetMap：
        {
          key: { age: 18 },
          value: Map
        }

      Map: 
        {
          key: key,
          value: Set
        }

      Set: [activeEffect] // activeEffect就是 effect 实例，而 effect 实例保存了更新用户界面的方法
*/
let targetMap = new WeakMap();

export function track(target, key) {
  if (!activeEffect) return;
  // 判断 weakmap 容器是否存过这个响应式数据
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    // 没有，才需要存一下
    /*
      weakmap -> 可以看做对象
        key: { age: 18 }
        value: Map 容器
    */
    targetMap.set(target, (depsMap = new Map()));
  }
  // 判断 map 容器是否存过某个属性
  let dep = depsMap.get(key);
  if (!dep) {
    // 没有，就将属性存储起来
    /*
      map ->
        key: 'age'
        value: Set 容器
    */
    depsMap.set(key, (dep = new Set()));
  }
  // 判断 set 容器是否存在某个 effect 实例
  if (!dep.has(activeEffect)) {
    // 没有，就将 effect 实例存储起来
    dep.add(activeEffect);
  }
}

/*
  3. effect：用来生成保存更新用户界面方法的 ReactiveEffect 实例（effect）
*/
export function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  // 一上来更新一次用户界面
  _effect.run();
  return _effect;
}

let activeEffect = null;

class ReactiveEffect {
  constructor(fn) {
    this.fn = fn;
  }
  run() {
    activeEffect = this;
    return this.fn();
  }
}

/*
  4. trigger：用来触发依赖更新
    如果你会vue2的响应式原理，那么可能知道一个dep.notify方法，用来触发响应式更新
    那么trigger就是做这件事的，找到更新数据对应的effect，调用run方法从而更新用户界面，达到响应式
*/
export function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach((effect) => effect.run());
  }
}
